package com.tpp.scheduler.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.tpp.comm.pojo.TpScheduler;
import com.tpp.comm.pojo.TpSchedulerSeat;
import com.tpp.comm.pojo.TpSchedulerSeatPrice;
import com.tpp.comm.pojo.TpTask;
import com.tpp.comm.util.TpConst;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tpp.scheduler.mapper.SchedulerSeatPriceMapper;
import com.tpp.scheduler.vo.OrderVo;
import com.tpp.scheduler.mapper.SchedulerMapper;
import com.tpp.scheduler.mapper.SchedulerSeatMapper;
import com.tpp.scheduler.mapper.TaskMapper;
import com.tpp.scheduler.service.ConfirmOrderService;
import com.tpp.scheduler.vo.RabbitOrderMessage;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ConfirmOrderServiceImpl implements ConfirmOrderService {

    @Resource
    private RedisTemplate<String,String> stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private SchedulerMapper schedulerMapper;

    @Resource
    private SchedulerSeatMapper schedulerSeatMapper;

    @Resource
    private SchedulerSeatPriceMapper schedulerSeatPriceMapper;

    @Resource
    private TaskMapper taskMapper;

    @Override
    @Transactional
    public String confirmOrder(OrderVo orderVo) {
        //解析座位字符串
        String[] seatPositions = orderVo.getSeatPositions().split(",");
        RLock[] rLocks = new RLock[seatPositions.length];
        for(int i=0;i<seatPositions.length;i++){
            String lockKey = TpConst.Redis.LOCK_SCHEDULEID_SEAT+orderVo.getSchedulerId()+":"+seatPositions[i];
            rLocks[i]=redissonClient.getLock(lockKey);
        }
        //锁住多个座位
        RLock multiLock = redissonClient.getMultiLock(rLocks);
        try{
            multiLock.lock();
            String orderNo = this.createOrder(orderVo);
            return orderNo;
        }catch (Exception e){
            log.error(e.getMessage());
            throw new RuntimeException(e.getMessage());
        } finally {
            multiLock.unlock();
        }
    }

    private String createOrder(OrderVo orderVo){
        String orderNo = IdUtil.getSnowflake(1,1).nextIdStr();
        //采用redis验证座位是否被占用
        BoundSetOperations<String,String> seatUsed = stringRedisTemplate.boundSetOps(TpConst.Redis.STOPE_SCHEDULED_USEDSEAT + ":" + orderVo.getSchedulerId());
        TpScheduler tpScheduler = schedulerMapper.selectById(orderVo.getSchedulerId());
        long expireSeconds = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))-tpScheduler.getEndTime().toEpochSecond(ZoneOffset.of("+8"));
        //设置过期时间

        seatUsed.expire(expireSeconds, TimeUnit.SECONDS);

        String[] seatPositions = orderVo.getSeatPositions().split(",");
        for (String seat : seatPositions) {
            if(seatUsed.isMember(seat)){
                throw new RuntimeException("该座位已被占用");
            }
        }

        List<String> priceList = new ArrayList<>();
        double amount=0;
        //数据库操作
        for (String seat : seatPositions) {
            String[] xy = seat.split("_");
            LambdaQueryWrapper<TpSchedulerSeat> seatLambdaQueryWrapper = Wrappers.lambdaQuery();
            seatLambdaQueryWrapper.eq(TpSchedulerSeat::getScheduleId,orderVo.getSchedulerId())
                    .eq(TpSchedulerSeat::getX,xy[0])
                    .eq(TpSchedulerSeat::getY,xy[1]);
            TpSchedulerSeat tpSchedulerSeat = schedulerSeatMapper.selectOne(seatLambdaQueryWrapper);
            tpSchedulerSeat.setOrderNo(orderNo);
            tpSchedulerSeat.setStatus(2);
            tpSchedulerSeat.setUserId(orderVo.getUserId());
            tpSchedulerSeat.setUpdatedTime(LocalDateTime.now());
            schedulerSeatMapper.updateById(tpSchedulerSeat);

            LambdaQueryWrapper<TpSchedulerSeatPrice> priceWrapper = Wrappers.lambdaQuery();
            priceWrapper.eq(TpSchedulerSeatPrice::getScheduleId,orderVo.getSchedulerId())
                    .eq(TpSchedulerSeatPrice::getAreaLevel,tpSchedulerSeat.getAreaLevel());
            TpSchedulerSeatPrice tpSchedulerSeatPrice = schedulerSeatPriceMapper.selectOne(priceWrapper);


            amount+=tpSchedulerSeatPrice.getPrice();
            priceList.add(tpSchedulerSeatPrice.getPrice()+"");
        }
        //向本地消息表添加信息
        TpTask task = new TpTask();
        task.setCreateTime(LocalDateTime.now());
        task.setMqExchange("");
        task.setErrorMsg("");
        task.setMqRoutingKey(TpConst.Rabbit.ORDER_QUEUE);
        task.setOrderNo(orderNo);
        task.setOvertime(LocalDateTime.now().plusMinutes(3));
        task.setStatus(0);
        task.setTaskType("确认订单");
        task.setTryCount(0);
        task.setVersion(0);
        ObjectMapper objectMapper = new ObjectMapper();
        RabbitOrderMessage message = new RabbitOrderMessage();
        BeanUtil.copyProperties(orderVo,message);
        message.setOrderNo(orderNo);
        String price = String.join("_", priceList);
        message.setPrice(price);
        if(orderVo.getIsNeedInsurance()==1){
            amount+=20;
        }
        message.setAmount(amount);
        try {
            String json = objectMapper.writeValueAsString(message);
            task.setRequestBody(json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        taskMapper.insert(task);
        seatUsed.add(seatPositions);
        return orderNo;
    }

}
